package hasthi.manager;

import hasthi.actuators.ActionCenter;
import hasthi.actuators.ActionContext;
import hasthi.actuators.ManagementAction;
import hasthi.common.BaseContext;
import hasthi.common.DistributedLogger;
import hasthi.common.HasthiException;
import hasthi.common.SafeScheduler;
import hasthi.common.Utils;
import hasthi.common.HasthiException.FaultCode;
import hasthi.common.constants.HasthiConstants;
import hasthi.common.constants.HasthiConstants.MngState;
import hasthi.container.UnicastContianier;
import hasthi.container.p2p.MulticastContianier;
import hasthi.coordinator.CoordinatorContext;
import hasthi.decision.RuleEngine;
import hasthi.metamodel.ChangeBuffer;
import hasthi.metamodel.MixedResourceModel;
import hasthi.metamodel.resource.ResourceModelContext;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;

import javax.xml.namespace.QName;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xmlbeans.XmlCursor;

/**
 * State kept in the MessageContext
 */
public class ManagerContext {
    private static Log log = LogFactory.getLog(HasthiConstants.LOGGER_NAME);
    private ActionRepository actionRepository;
    private Event2ActionRepository event2ActionRepository;
    private final ManagerAddress myAddress;
    private MixedResourceModel resourceModel;
    private ResourceModelContext resourceModelContext;

    private ManagerAddress coordinatorAddress;

    private List<QName> assignedResources = new ArrayList<QName>();

    

    private ManagerAddress[] sucessorList = new ManagerAddress[0];

    private RuleEngine ruleEngine;

    private Map<QName, String> name2mangEndpointMap = new Hashtable<QName, String>();

    private MulticastContianier multicastContianer;

    private UnicastContianier unicastContainer;

    private CoordinatorContext coordinatorContext;

//    private boolean isCoordinator = false;
//    private boolean joinedCoordinator = false;
    private MngState managerState = MngState.Started;
    private boolean amICoordinator = false;
    private boolean isNominated  = false;
    //private CoordState coordState = CoordState.OnlyManager; 
    

//    private InetSocketAddress boostrapNode;
    private BaseContext baseContext;
    private int managementRingSize = 0;
    
    private long coordinatorTimeStamp = -1;
    private ManagerAddress oldCoordinator;
    
    
    private ManagementDemon managementDemon;
    private final ActionContext actionContext;
    private final SafeScheduler scheduler;
    private ScheduledExecutorService scheduledExecutorService;
    //private ElectionSet electionSet;
    
    private boolean firstManagerOfRing = false;
    private String id; 
    private final ElectionController electionController;
    private Map<QName, ManagedResourceType> resourceDiffMap = new HashMap<QName, ManagedResourceType>();

    public ManagerContext() throws HasthiException{
        this((Properties)null);
    }
    
    public ManagerContext(Properties properties) throws HasthiException{
        this(new BaseContext(properties));
    }
    
    
    public ManagerContext(BaseContext baseContext) throws HasthiException {
        this.baseContext = baseContext;
        resourceModelContext = new ResourceModelContext(this,new ChangeBuffer());
        resourceModel = new MixedResourceModel(resourceModelContext);
        String localRuleSet = baseContext.getRuleset();
        log.info("Using local rules from "+ localRuleSet);
        
        actionContext = new ActionContext(baseContext,false);
        actionContext.setManagerContext(this);
        actionContext.setResourceModel(resourceModel);
        ActionCenter actionCenter = baseContext.createActuatorBase();
        actionCenter.init(actionContext);
        ruleEngine = new RuleEngine(localRuleSet,actionCenter);
        actionContext.setRuleEngine(ruleEngine);
        
        scheduler = new SafeScheduler(50,new SafeScheduler.JobCancelledCallback() {
            public void jobCancelled(Runnable runnable) {
                actionContext.getPlanner().actionFailed((ManagementAction)runnable, null);
            }
        },false);
        scheduledExecutorService = Executors.newScheduledThreadPool(2);
        actionContext.setScheduler(scheduler);
        myAddress = ManagerAddress.Factory.newInstance();
        myAddress.setRank(Integer.MAX_VALUE);
        electionController = new ElectionController(this);
    }

    public ActionRepository getActionRepository() {
        return actionRepository;
    }

    public Event2ActionRepository getEvent2ActionRepository() {
        return event2ActionRepository;
    }


    public ManagerAddress getCoordinatorAddress() {
        return coordinatorAddress;
    }

    public synchronized void setCoordinatorAddress(ManagerAddress coordinatorAddress,long coordinatorTimeStamp) throws HasthiException {
        if(coordinatorAddress == null){
            throw new HasthiException("Coordinator Must not be Null, at Set Coordinator ",FaultCode.LocalError);
        }else if(coordinatorTimeStamp <= 0){
            throw new HasthiException("Coordinator Timestep Must not be Zero or ngative, at Set Coordinator it was "+ coordinatorTimeStamp,FaultCode.LocalError);            
        }else if(coordinatorAddress.getRank() >  getMyRank()){
            throw new HasthiException("Wrong coordinator set, his rank ="+ coordinatorAddress.getRank() + 
                    " > my rank = "+ getMyRank(),FaultCode.LocalError);
        }
        this.coordinatorAddress = coordinatorAddress;
        this.coordinatorTimeStamp = coordinatorTimeStamp;
        this.managerState = MngState.HasCoordinatorNotJoined;
    }

    public ManagerAddress getMyAddress() {
        return myAddress;
    }

     public int getMyRank() {
        return myAddress.getRank();
    }

    public void setMyRank(int myRank) {
        myAddress.setRank(myRank);
        id = Utils.getID(myAddress);
    }

    public MixedResourceModel getResourceModel() {
        return resourceModel;
    }

    public ManagerAddress[] getSucessorList() {
        return sucessorList;
    }

    public RuleEngine getRuleEngine() {
        return ruleEngine;
    }

    public Map<QName, String> getName2mangEndpointMap() {
        return name2mangEndpointMap;
    }

    public MulticastContianier getMulticastContianer() {
        return multicastContianer;
    }

    public void setMulticastContianer(MulticastContianier contianer) {
        multicastContianer = contianer;
        myAddress.setP2Paddress(multicastContianer.getContainierAddress());
        actionContext.setMulticastContainer(contianer);
        id = Utils.getID(myAddress);
    }

    public UnicastContianier getUnicastContainer() {
        return unicastContainer;
    }

    public void setUnicastContainer(UnicastContianier soapContainer) {
        this.unicastContainer = soapContainer;
        String soapAddress = soapContainer.getContainierAddress();
        myAddress.setSoapAddress(soapAddress);
        resourceModelContext.setContainer(soapContainer);
        this.baseContext.setName(myAddress.getSoapAddress());
        actionContext.setUnicastContainer(soapContainer);
        id = Utils.getID(myAddress);
    }

    public CoordinatorContext getCoordinatorContext() {
        return coordinatorContext;
    }

  


    public boolean amICoordinator() {
        return amICoordinator;
    }

    public synchronized void resignCoordinator(ManagerAddress coordinatorAddress,long coordinatorTimeStamp) throws HasthiException {
        log.info(myAddress.getSoapAddress() + " resigned in favour of "+ coordinatorAddress.getSoapAddress());
        managerState = MngState.HasCoordinatorNotJoined;
        
        coordinatorContext.getCoordinationDemon().stopDeamon();
        amICoordinator = false;
        isNominated = false;
        setCoordinatorAddress(coordinatorAddress, coordinatorTimeStamp);
        //TODO may be there are more to be done, this is only need
        // if we going to handle the paritions
        //TODO notify the new coordinator and join the managerment ring and then change state to HasCoordinatorJoined
    }

//    public InetSocketAddress getBoostrapNode() {
//        return boostrapNode;
//    }
    
    public synchronized void becomeCoordinator(long coordinatorTimestamp) throws HasthiException{
        if(myAddress.getRank() == Integer.MAX_VALUE){
            setMyRank(1);
        }
        if(amICoordinator){
            throw new HasthiException(this + " is already coordinator, become coordinator invoked twice!!",FaultCode.LocalError);
        }
        //set coordinator context and Start Coordinator Deamon
        coordinatorContext = new CoordinatorContext(this);
        amICoordinator = true;
        this.isNominated = false;
        setCoordinatorAddress(myAddress, coordinatorTimestamp);
    }

    public BaseContext getBaseContext() {
        return baseContext;
    }

    public void setSucessorList(ManagerAddress[] sucessorList) {
        this.sucessorList = sucessorList;
    }

    public int getManagementRingSize() {
        return managementRingSize;
    }

    public void setManagementRingSize(int managementRingSize) {
        this.managementRingSize = managementRingSize;
    }

    public ResourceModelContext getResourceModelContext() {
        return resourceModelContext;
    }

    public List<QName> getAssignedResources() {
        return assignedResources;
    }

    public long getCoordinatorTimeStamp() {
        return coordinatorTimeStamp;
    }
    
    public synchronized void setCoordinatorDown(){
        if(coordinatorAddress != null){
            oldCoordinator = coordinatorAddress;
            coordinatorAddress = null;
        }
        managerState = MngState.NoCoordWithoutElection;
    }

    public ManagerAddress getOldCoordinator() {
        return oldCoordinator;
    }
  
    
    public hasthi.manager.ManagerContextDocument.ManagerContext toXml() throws HasthiException{
        hasthi.manager.ManagerContextDocument.ManagerContext context 
            = hasthi.manager.ManagerContextDocument.ManagerContext.Factory.newInstance();
        
        context.setAddress(getMyAddress());
        context.setCoordinatorAddress(getCoordinatorAddress());
        context.setAssignedResourceArray(assignedResources.toArray(new QName[0]));
        context.setCoordinatorTimeStamp(getCoordinatorTimeStamp());
        //context.setElectionInProgress(lastElectionTime != -1);
        
//        for(ElectionData electionData:elections){
//            ManagerContextDocument.ManagerContext.ElectionData electionDataXbean = context.addNewElectionData();
//            electionDataXbean.setPurposedCoordinator(electionData.getPurposedCoordinator());
//            electionDataXbean.setStartTime(electionData.getStartTime());
//        }
        context.setManagementRingSize(getManagementRingSize());
        if(amICoordinator()){
            context.setManagementRing(coordinatorContext.toXml());
        }
        return context;
    }
    
    public synchronized ManagementDemon getManagementDemon() {
        if(managementDemon == null){
            managementDemon =  new ManagementDemon(this);
        }
        return managementDemon;
    }

//    public boolean isJoinedCoordinator() {
//        return joinedCoordinator;
//    }

    public void setJoinedCoordinator() {
        this.managerState = MngState.HasCoordinatorJoined;
    }
    
    public DistributedLogger getDlogger(){
        return baseContext.getDlogger();
    }

    public SafeScheduler getScheduler() {
        return scheduler;
    }

    public MngState getManagerState() {
        return managerState;
    }

    public boolean isFirstManagerOfRing() {
        return firstManagerOfRing;
    }

    public void setFirstManagerOfRing(boolean firstManagerOfRing) {
        this.firstManagerOfRing = firstManagerOfRing;
    }
    
    public boolean hasCoordinator(){
        return managerState.equals(MngState.HasCoordinatorJoined) 
        || managerState.equals(MngState.HasCoordinatorNotJoined);
    }
    
    
    
    public boolean isNominted() {
        return isNominated;
    }

    public void setNominted(boolean amINominted) {
        this.isNominated = amINominted;
    }

    @Override
    public String toString() {
        return id;
    }

    public ElectionController getElectionController() {
        return electionController;
    }
    
    public Map<QName, ManagedResourceType> getResourceDiffMap() {
        return resourceDiffMap;
    }

    public ScheduledExecutorService getScheduledExecutorService() {
        return scheduledExecutorService;
    }
}
